local assets = {
    Asset("ANIM", "anim/rg_lavaarena_sunder_armor.zip"),
    -- rg_lavaarena_sunder_armor -> pre loop pst

    Asset("ANIM", "anim/rg_poison.zip"),
    -- rg_poison -> level[1-4]_[pre|loop|pst]

    Asset("ANIM", "anim/rg_elec_charged_fx.zip"),
    -- rg_elec_charged_fx -> discharged
    Asset("ANIM", "anim/rg_elec_hit_fx.zip"),
    -- rg_elec_hit_fx -> sparks[1-3]
}

local function common()
    local inst = CreateEntity()
    local trans = inst.entity:AddTransform()
    local anim = inst.entity:AddAnimState()

    inst:AddTag("fx")
    inst:AddTag("noclick")
    inst:AddTag("rgTag_debuff_fx")

    inst.persist = false

    return inst, anim, trans
end

local function hurtspawn(inst)
    inst.AnimState:PlayAnimation("pre")
    inst.AnimState:PushAnimation("loop")
    inst:Show()
end

local function hurtdespawn(inst)
    inst.AnimState:PlayAnimation("pst")
    inst:DoTaskInTime(12/30, inst.Hide)
end

local function hurtfx()
    local inst, anim, trans = common()
    anim:SetBank("rg_lavaarena_sunder_armor")
    anim:SetBuild("rg_lavaarena_sunder_armor")

    inst.SpawnFn = hurtspawn
    inst.DespawnFn = hurtdespawn

    return inst
end

--------

local function firesetlevel(inst, level)
    assert(level)
    assert(level <= 3)
    inst.level = level
end

local function firespawn(inst)
    inst.components.firefx:SetLevel(inst.level)
end

local function firedespawn(inst)
    inst.components.firefx:Extinguish()
end

local function firefx()
    -- local firelevels = 
    -- {
    --     {anim="loop_small", pre="pre_small", pst="post_small", sound="dontstarve/common/campfire", radius=2, intensity=.6, falloff=.7, colour = {197/255,197/255,170/255}, soundintensity=1},
    --     {anim="loop_med", pre="pre_med", pst="post_med",  sound="dontstarve/common/treefire", radius=3, intensity=.75, falloff=.5, colour = {255/255,255/255,192/255}, soundintensity=1},
    --     {anim="loop_large", pre="pre_large", pst="post_large",  sound="dontstarve/common/forestfire", radius=4, intensity=.8, falloff=.33, colour = {197/255,197/255,170/255}, soundintensity=1},        
    -- }
    local inst = SpawnPrefab("character_fire")

    inst:AddTag("rgTag_debuff_fx")

    -- inst:AddComponent("heater")
    -- inst.components.heater.heatfn = GetHeatFn
    inst:RemoveComponent("heater")

    inst.level = 1
    inst.SetLevel = firesetlevel
    inst.SpawnFn = firespawn
    inst.DespawnFn = firedespawn
end

---------

local function setlevel(inst, level)
    inst.level = level
    if inst.shown then
        inst.AnimState:PlayAnimation("level"..inst.level.."_loop")
    end
end

local function poisonspawn(inst)
    inst.shown = true
    inst:Show()

    inst.AnimState:PlayAnimation("level"..inst.level.."_pre")
    inst.AnimState:PushAnimation("level"..inst.level.."_loop")
end

local function poisondespawn(inst)
    inst.shown = false
    inst.AnimState:PlayAnimation("level"..inst.level.."_pst")
    inst:DoTaskInTime(50/30, inst.Hide)
end

local function poisonfx()
    local inst, anim, trans = common()
    anim:SetBank("rg_poison")
    anim:SetBuild("rg_poison")

    inst.level = 1
    inst.shown = false

    inst.SpawnFn = poisonspawn
    inst.DespawnFn = poisondespawn
    -- inst.SetLevel = setlevel
    inst.StackChangedFn = setlevel

    return inst
end

----

local function onupdate(inst, dt)
    if inst.sound then
        inst.sound = nil
        inst.SoundEmitter:PlaySound("dontstarve/characters/wx78/spark")
    end

    inst.Light:SetIntensity(inst.i)
    inst.i = inst.i - dt * 2
    if inst.i <= 0 then
        if inst.killfx then
            inst:Remove()
        else
            inst.task:Cancel()
            inst.task = nil
        end
    end
end

local function OnAnimOver(inst)
    if inst.task == nil then
        inst:Remove()
    else
        inst:RemoveEventCallback("animover", OnAnimOver)
        inst.killfx = true
    end
end

local function OnRemoveFlash(inst)
    if inst.target.components.colouradder == nil and inst.target:IsValid() then
        if inst.target.components.freezable ~= nil then
            inst.target.components.freezable:UpdateTint()
        else
            inst.target.AnimState:SetAddColour(0, 0, 0, 0)
        end
    end
end

local function OnUpdateFlash(inst)
    if inst.target == nil or not inst.target:IsValid() then
        inst.OnRemoveEntity = nil
        inst:RemoveComponent("updatelooper")
    elseif inst.flash > .1 then
        inst.flash = inst.flash - .075
        inst.blink = inst.blink < 3 and inst.blink + 1 or 0
        local c = inst.flash * (inst.blink < 2 and 0.25 or 0 )
        if inst.intensity ~= nil then
            c = inst.flash * inst.intensity * (inst.blink < 2 and 1 or 0.25)
        end
        if inst.target.components.colouradder ~= nil then
            inst.target.components.colouradder:PushColour(inst, 1, 1, 1, c)
        else
            inst.target.AnimState:SetAddColour(1, 1, 1, c)
        end
    else
        if inst.target.components.colouradder ~= nil then
            inst.target.components.colouradder:PopColour(inst)
        elseif inst.target.components.freezable ~= nil then
            inst.target.components.freezable:UpdateTint()
        else
            inst.target.AnimState:SetAddColour(0, 0, 0, 0)
        end
        inst.OnRemoveEntity = nil
        inst:RemoveComponent("updatelooper")
    end
end

local function AlignToTarget(inst, target, attacker, flash)
    --NOTE: attacker could be a projectile
    local x, y, z = target.Transform:GetWorldPosition()
    local x1, y1, z1 = attacker.Transform:GetWorldPosition()
    local dx, dz = x1 - x, z1 - z
    local len = math.sqrt(dx * dx + dz * dz)
    local r = len ~= 0 and (target:GetPhysicsRadius(0) + .2) / len or 0
    inst.Transform:SetPosition(x + dx * r, y + 1, z + dz * r)

    if flash and inst.components.updatelooper == nil then
        inst.OnRemoveEntity = OnRemoveFlash
        inst.target = target
        inst.flash = 1
        inst.blink = 0
        inst.intensity = target:HasTag("largecreature") and .1 or .2

        inst:AddDSTComponent("updatelooper")
        inst.components.updatelooper:AddOnUpdateFn(OnUpdateFlash)
        OnUpdateFlash(inst)
    end
end


local function sparkfn()
    local inst = common()
    
    inst.entity:AddSoundEmitter()
    inst.entity:AddLight()

    inst.Transform:SetScale(2, 2, 2)

    inst.AnimState:SetBank("rg_elec_hit_fx")
    inst.AnimState:SetBuild("rg_elec_hit_fx")
    inst.AnimState:PlayAnimation("sparks_"..math.random(3))
    inst.AnimState:SetBloomEffectHandle("shaders/anim.ksh")

    inst:DoTaskInTime(1, inst.Remove)

    inst.AlignToTarget = AlignToTarget

    inst.Light:Enable(true)
    inst.Light:SetRadius(2)
    inst.Light:SetFalloff(1)
    inst.Light:SetIntensity(.9)
    inst.Light:SetColour(235 / 255, 121 / 255, 12 / 255)

    local dt = 1 / 20
    inst.i = .9
    inst.sound = true
    inst.task = inst:DoPeriodicTask(dt, onupdate, nil, dt)

    inst:ListenForEvent("animover", OnAnimOver)

    return inst
end

----

local onupdate = onupdate -- borrow
local OnAnimOver = OnAnimOver -- borrow
local OnRemoveFlash = OnRemoveFlash -- borrow
local OnUpdateFlash = OnUpdateFlash -- borrow

local function electriccharge()
    local inst = common()

    inst.entity:AddSoundEmitter()
    inst.entity:AddLight()

    inst.AnimState:SetBank("rg_elec_charged_fx")
    inst.AnimState:SetBuild("rg_elec_charged_fx")
    inst.AnimState:PlayAnimation("discharged")
    inst.AnimState:SetBloomEffectHandle("shaders/anim.ksh")

    inst.Light:Enable(true)
    inst.Light:SetRadius(2)
    inst.Light:SetFalloff(1)
    inst.Light:SetIntensity(.9)
    inst.Light:SetColour(235 / 255, 121 / 255, 12 / 255)

    local dt = 1 / 20
    inst.i = .9
    inst.task = inst:DoPeriodicTask(dt, onupdate, nil, dt)

    inst.flash = 1
    inst.blink = 0
    inst.sound = true

    -- inst:AddComponent("updatelooper")
    inst:AddDSTComponent("updatelooper")
    inst.components.updatelooper:AddOnUpdateFn(OnUpdateFlash)

    inst:ListenForEvent("animover", OnAnimOver)
    inst.OnRemoveEntity = OnRemoveFlash

    return inst
end

local function zap(inst)
    local fx = electriccharge()
    fx.target = inst.target
    inst:AddChild(fx)

    local fx = sparkfn()
    -- fx.target = inst.target
    fx.Transform:SetPosition(0,1,0)
    inst:AddChild(fx)
end

local function electricspawn(inst)
    zap(inst)
    inst.shown = true
end

local function electricdespawn(inst)
    inst:Hide()
    inst.shown = false
end

local function electricpulse(inst)
    if inst.shown then
        zap()
    end
end

local function electricfx()
    local inst = common()

    inst.target = nil
    inst.SpawnFn = electricspawn
    inst.DespawnFn = electricdespawn
    inst.PulseFn = electricpulse

    return inst
end

------

local function firefx()
    local inst = SpawnPrefab("character_fire")

    inst:AddTag("rgTag_debuff_fx")

end

------
local defaultColor = Vector3(0, 0, 0)
local frozenColor = Vector3(82/255,115/255,124/255)
local maxstack = 3
local function getaddcolour(stack)
    local p = math.min(stack/maxstack, 1)
    return Vector3(
        Lerp(defaultColor.x, frozenColor.x, p),
        Lerp(defaultColor.y, frozenColor.y, p),
        Lerp(defaultColor.z, frozenColor.z, p))
end

local function coldstackchanged(inst, stack)
    if stack ~= inst.stack then
        stack = inst.stack
        if inst.target and inst.target:IsValid() then
            if not inst.target.components.highlight then
                inst.target:AddComponent("highlight")
            end
            inst.target.components.highlight:SetAddColour(getaddcolour(stack))
        end
    end
end

local function coldspawn(inst)
    --@-- fx?
    coldstackchanged(inst, 1)
end

local function colddespawn(inst)
    coldstackchanged(inst, 0)
end

local function coldfx()
    local inst = common()

    inst.target = nil
    inst.stack = 0

    inst.SpawnFn = coldspawn
    inst.DespawnFn = colddespawn
    inst.StackChangedFn = coldstackchanged

    return inst
end

------

local function icespawn(inst)
    -- local shatterlevels = 
    -- {
    --     {anim="tiny"},
    --     {anim="small"},
    --     {anim="medium"},
    --     {anim="large"},        
    --     {anim="huge"},
    -- }
    local fx = SpawnPrefab("shatter")
    fx.Transform:SetPosition(inst.Transform:GetWorldPosition())
    fx.components.shatterfx:SetLevel(inst.level)
end

local function icesetlevel(inst, level)
    assert(level)
    assert(level <= 5)
    inst.level = level
end

local function icedespawn(inst)
    return true
end

local function icefx()
    local inst = common()
    inst.level = level

    inst.SetLevel = icesetlevel
    inst.SpawnFn = icespawn
    inst.DespawnFn = icedespawn

    return inst
end

return 
    Prefab("rg_debuff_fx_hurt", hurtfx, assets),
    Prefab("rg_debuff_fx_poison", poisonfx, assets),
    Prefab("rg_debuff_fx_electric", electricfx, assets),
    Prefab("rg_electric_sparks", sparkfn, assets),
    Prefab("rg_debuff_fx_cold", coldfx, nil),
    Prefab("rg_debuff_fx_ice", icefx, assets)
